//////////////////////////////////////////////////////////////////////
// ParticleSystem.hpp
//
// SHEN Fangyang
// me@shenfy.com
//
// Copyright (C) SHEN Fangyang, 2011, All rights reserved.
//////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////
//  Brief Description
//
//	CPU managed particle system implementation
//
//////////////////////////////////////////////////////////////////////
#include "../include/ParticleSystem.h"
#include <Logger.h>
#include "../include/D3D10Engine.h"
#include "../include/Texture.h"
#include <D3DX10.h>

namespace grflib { namespace engine { namespace aux {

template<typename TP>
CParticleSystem<TP>::CParticleSystem(std::size_t maxParticleNum)
    : m_maxParticleNum(maxParticleNum),
    m_pVertexBuffer(0),
    m_population(0),
    m_pIndexBuffer(0)
{
    m_particleVertices.reset(new TP::vertex_type[maxParticleNum]);
    m_particleIndices.reset(new DWORD[maxParticleNum]);
    for (size_t i = 0; i < maxParticleNum; i++)
        m_particleIndices[i] = i;
}

template<typename TP>
CParticleSystem<TP>::CParticleSystem()
    : m_maxParticleNum(100),
    m_pVertexBuffer(0),
    m_population(0),
    m_pIndexBuffer(0)
{
    m_particleVertices.reset(new TP::vertex_type[m_maxParticleNum]);
    m_particleIndices.reset(new DWORD[m_maxParticleNum]);
    for (size_t i = 0; i < m_maxParticleNum; i++)
        m_particleIndices[i] = i;
}

template<typename TP>
CParticleSystem<TP>::~CParticleSystem()
{
    OnDestroy();
    m_particleVertices.reset();
    m_particleIndices.reset();
    m_particles.clear();
}

template<typename TP>
void CParticleSystem<TP>::ResetMaximumParticleNum(std::size_t maxNum)
{
    m_maxParticleNum = maxNum;
    m_particleVertices.reset(new TP::vertex_type[m_maxParticleNum]);
    m_particleIndices.reset(new DWORD[m_maxParticleNum]);
    for (size_t i = 0; i < m_maxParticleNum; i++)
        m_particleIndices[i] = i;

    //re-create vertex buffer
    DestroyVertexBuffer();
    CreateVertexBuffer();
}

template<typename TP>
HRESULT CParticleSystem<TP>::CreateVertexBuffer(ID3D10Device *pd3dDevice)
{
    HRESULT hr;
    
    D3D10_BUFFER_DESC bd;

    bd.Usage = D3D10_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(TP::vertex_type) * m_maxParticleNum;
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    V_RETURN( pd3dDevice->CreateBuffer(&bd, 0, &m_pVertexBuffer) );

    bd.Usage = D3D10_USAGE_DEFAULT;
    bd.ByteWidth = sizeof(DWORD) * m_maxParticleNum;
    bd.BindFlags = D3D10_BIND_INDEX_BUFFER;
    bd.CPUAccessFlags = 0;
    bd.MiscFlags = 0;
    V_RETURN( pd3dDevice->CreateBuffer(&bd, 0, &m_pIndexBuffer) );

    return S_OK;
}

template<typename TP>
HRESULT CParticleSystem<TP>::OnCreateDevice(ID3D10Device *pd3dDevice, 
    const DXGI_SURFACE_DESC *pBufferSurfaceDesc)
{
    HRESULT hr;
    V_RETURN( CreateVertexBuffer(pd3dDevice) );

    return S_OK;
}

template<typename TP>
void CParticleSystem<TP>::DestroyVertexBuffer()
{
    SAFE_RELEASE(m_pVertexBuffer);
    SAFE_RELEASE(m_pIndexBuffer);
}

template<typename TP>
void CParticleSystem<TP>::OnDestroy()
{
    DestroyVertexBuffer();
}

template<typename TP>
void CParticleSystem<TP>::FrameMove(double fTime, float fElapsedTime)
{
    //advance particles
    m_bbox.Clear();
    m_population = 0;

    particle_list_type::iterator iter = m_particles.begin();
    while (iter != m_particles.end())
    {
        if (iter->vertex.life < 0.0f) //is embryo
        {
            if (fTime >= iter->birthTime) //time to give birth
            {
                iter->vertex.life = 0.0f;
                m_bbox.AddMinMax(0, iter->vertex.pos.x);
                m_bbox.AddMinMax(1, iter->vertex.pos.y);
                m_bbox.AddMinMax(2, iter->vertex.pos.z);
                if (m_population < m_maxParticleNum)
                    memcpy(&m_particleVertices[m_population ++], &(iter->vertex), sizeof(TP::vertex_type));
            }
            ++iter;
        }
        else if (iter->vertex.life > 1.0f) //time to die
        {
            iter->deathFunc(m_particles, fTime, fElapsedTime, &(*iter)); //call particle's OnDeath method
            iter = m_particles.erase(iter);
        }
        else //is alive, advance it
        {
            iter->updateFunc(fTime, fElapsedTime, &(*iter)); //call particle's advancing method
            m_bbox.AddMinMax(0, iter->vertex.pos.x);
            m_bbox.AddMinMax(1, iter->vertex.pos.y);
            m_bbox.AddMinMax(2, iter->vertex.pos.z);
            if (m_population < m_maxParticleNum)
                memcpy(&m_particleVertices[m_population ++], &(iter->vertex), sizeof(TP::vertex_type));
            ++iter;
        }
    }

    //update vertex/index buffer
    core::CEngine::Instance().GetD3DDevice()->UpdateSubresource(m_pVertexBuffer, 0, 0, m_particleVertices.get(), 0, 0);
    core::CEngine::Instance().GetD3DDevice()->UpdateSubresource(m_pIndexBuffer, 0, 0, m_particleIndices.get(), 0, 0);
}

template<typename TP>
HRESULT CParticleSystem<TP>::PreRender(ID3D10Device *pd3dDevice,
    const D3DXMATRIX *pWorldMat,
    double fTime,
    float fElapsedTime,
    UINT pass)
{
    using namespace grflib::engine::core;
    if (0 == m_population) return S_OK;

    HRESULT hr;

    bool bSucceeded = false;
    boost::shared_ptr<CEffect> pEffect;

    ////////////////////////////////////////
    //get technique
    if (pEffect = boost::dynamic_pointer_cast<CEffect>(_ResrcMan.FindObj(m_effectName)))
    {
        m_pEffect = pEffect;
        if (m_pTech = pEffect->GetTechnique(m_techniqueName))
        {
            V_RETURN( m_pTech->GetDesc(&m_techDesc) );
            bSucceeded = true;
        }
    }
    if (!bSucceeded)
    {
        W32ERR("Failed to switch to technique: %s/%s", m_effectName.c_str(), m_techniqueName.c_str());
        return E_FAIL;
    }

    ////////////////////////////////////////
    //get camera
    boost::shared_ptr<CCamera> &pCurCamera = _CamMan.GetCurrentCamera();
    if (!pCurCamera) //current camera not set
    {
        W32ERR(_T("Current camera not set!"));
        return E_FAIL;
    }

    ////////////////////////////////////////
    //set object independent shader variables
    ID3D10EffectMatrixVariable *pWorldMatVar = pEffect->GetShaderVariable("World")->AsMatrix();
    ID3D10EffectMatrixVariable *pViewMatVar = pEffect->GetShaderVariable("View")->AsMatrix();
    ID3D10EffectMatrixVariable *pProjMatVar = pEffect->GetShaderVariable("Projection")->AsMatrix();
    ID3D10EffectMatrixVariable *pInvViewMatVar = pEffect->GetShaderVariable("InvView")->AsMatrix();
    if (!pWorldMatVar || !pViewMatVar || !pProjMatVar || !pInvViewMatVar) //uncompatible shader
    {
        W32ERR(_T("Shader uncompatible with particle system!"));
        return E_FAIL;
    }

    V_RETURN( pWorldMatVar->SetMatrix( (float*)pWorldMat ) );
    V_RETURN( pViewMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetViewMatrix() ) );
    V_RETURN( pProjMatVar->SetMatrix( (float*)pCurCamera->GetDXUTCamera()->GetProjMatrix() ) );

    D3DXMATRIX invViewMat;
    D3DXMatrixInverse(&invViewMat, 0, pCurCamera->GetDXUTCamera()->GetViewMatrix());
    V_RETURN( pInvViewMatVar->SetMatrix( (float*)invViewMat ) );

    return S_OK;
}

template<typename TP>
HRESULT CParticleSystem<TP>::Render(ID3D10Device *pd3dDevice,
    const D3DXMATRIX *pWorldMat,
    double fTime,
    float fElapsedTime,
    UINT pass)
{
    using namespace grflib::engine::core;
    if (0 == m_population) return S_OK;

    HRESULT hr;

    boost::shared_ptr<CEffect> pEffect;
    if (!(pEffect = m_pEffect.lock())) //hold reference until end of function
    {
        W32ERR(_T("Effect unexpectedly lost!"));
        return E_FAIL;
    }

    ////////////////////////////////////////
    // draw particles
    ID3D10EffectPass *pPass;
    D3D10_PASS_DESC passDesc;

    for (UINT pass = 0; pass < m_techDesc.Passes; pass++)
    {
        pPass = m_pTech->GetPassByIndex(pass);
        V_RETURN( pPass->GetDesc(&passDesc) );

        //set material
        boost::shared_ptr<CMaterial> &pMaterial = boost::dynamic_pointer_cast<CMaterial>(_ResrcMan.FindObj(m_materialName));
        V_RETURN( pEffect->SetShaderVariables(pMaterial, m_techniqueName) );

        //set IA stage
        V_RETURN( _IAStage.SetInputLayout(
            pEffect->GetVSSignature(pPass),
            passDesc.pIAInputSignature,
            passDesc.IAInputSignatureSize,
            TP::GetVertexLayout()) );
        V_RETURN( pPass->Apply(0) );

        //set IA stage status
        _IAStage.SetVertexBuffer(0, m_pVertexBuffer, sizeof(typename TP::vertex_type), 0);
        _IAStage.SetIndexBuffer(m_pIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
        _IAStage.SetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);

        pd3dDevice->DrawIndexed(m_population, 0, 0);
    } //end of a technique-pass

    return S_OK;
}

template<typename TP>
grflib::engine::core::BBox<float> &CParticleSystem<TP>::GetBoundingBox()
{
    return m_bbox;
}

template<typename TP>
void CParticleSystem<TP>::AddParticle(const TP &particle)
{
    m_particles.push_back(particle);
}


}}} // namespaces